Skip to content

fix(webview): isolate parallel provider view state#909

Open
easonLiangWorldedtech wants to merge 11 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:fix/tab-mode-model-isolation
Open

fix(webview): isolate parallel provider view state#909
easonLiangWorldedtech wants to merge 11 commits into
Zoo-Code-Org:mainfrom
easonLiangWorldedtech:fix/tab-mode-model-isolation

Conversation

@easonLiangWorldedtech

@easonLiangWorldedtech easonLiangWorldedtech commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes multi-tab mode/model settings being overwritten by global state from other tabs. Each ClineProvider instance (sidebar, editor tab) now maintains its own isolated mode, apiConfiguration, and profile selection via a per-view local state buffer with view-specific persistence keys.

Problem

In multi-tab mode, when users switch between Roo Code tabs with different mode/API configuration selections, the active tabs settings get overwritten by the global state from another tab. This happens because all tabs share the same ContextProxy singleton — any handleModeSwitch() or activateProviderProfile() call in one tab updates shared global state that other tabs then read.

Solution

Implemented view-local state isolation using a per-provider-instance buffer with view-specific persistence keys:

Architecture

  • Added nextViewId static counter for stable, monotonically increasing view IDs ({renderContext}-{counter})
  • Implemented viewLocalState: Partial<ExtensionState> buffer per provider instance
  • Introduced view-specific ContextProxy keys — each tab persists its state under __view_state_{viewId}_{key} so recreated views restore their own values instead of the last writer shared state
  • Modified getState() to merge viewLocalState on top of global state with proper precedence

Key Changes in src/core/webview/ClineProvider.ts:

Method Change
loadViewState() Reads from view-specific keys first (__view_state_{id}_mode), falls back to shared keys for backward compatibility. Only snapshots per-view fields (mode, currentApiConfigName, apiConfiguration) — project-level settings like customModePrompts and modeApiConfigs are read fresh from ContextProxy each time
saveViewState(key, value) Persists mode/currentApiConfigName/apiConfiguration to view-specific ContextProxy key instead of global key
_updateViewLocalStateFromMutation(values) New helper — syncs viewLocalState when ContextProxy is mutated via setValues(), setValue(), profile upsert/activation/deletion, or resetState()
setupGlobalStateListener() Listens for VSCode config changes to reload viewLocalState when other views modify global state
handleModeSwitch() Calls _updateViewLocalStateFromMutation + reads currentApiConfigName from getState() (not direct global read) before updateGlobalState()
activateProviderProfile() / deleteProviderProfile() Uses _updateViewLocalStateFromMutation for consistent sync
createTaskWithHistoryItem() Preserves restored task mode in both global state AND viewLocalState; reads currentApiConfigName from merged state
setValue() / setValues() Delegates to ContextProxy then calls _updateViewLocalStateFromMutation
resetState() Calls _clearViewLocalState() + broadcastResetToAllInstances() after resetting ContextProxy
broadcastResetToAllInstances() New — clears view-local state in all other active instances and posts updated state

View-Specific Persistence Keys:

// Each tab persists under its own key so recreation restores correct values
__view_state_sidebar-0_mode
__view_state_sidebar-0_currentApiConfigName
__view_state_editor-1_apiConfiguration
// ...etc

Merge Precedence in getState():

const mergedStateValues = { ...stateValues, ...this.viewLocalState }
// apiConfiguration: { ...providerSettings, ...mergedStateValues.apiConfiguration }
// All other fields use mergedStateValues for local override support

Design Decisions (Per-View vs Project-Level Shared)

Field Scope Rationale
mode Per-view Users want Tab A in Code mode, Tab B in Architect mode
currentApiConfigName / apiConfiguration Per-view Each tab may use different models/providers
customModePrompts Project-level shared Custom mode role definitions should be consistent across all tabs
modeApiConfigs (mode→profile mapping) Project-level shared Acts as project default; per-tab selection is a future enhancement
customInstructions, allowedCommands, mcpServers Project-level shared Global settings that should apply to all tabs uniformly

Additional Changes (beyond ClineProvider)

File Change
src/extension/api.ts API.setConfiguration() now calls sidebarProvider.setValues(values) instead of directly calling contextProxy.setValues(), ensuring _updateViewLocalStateFromMutation is triggered
webview-ui/src/utils/vscode.ts Added persistent viewStateId to webview launch handshake — each tab gets a unique ID stored in ContextProxy for cross-session persistence
webview-ui/src/App.tsx / ExtensionStateContext.tsx Pass and consume viewStateId from extension host context
src/core/config/importExport.ts Settings import now calls broadcastResetToAllInstances() (wrapped in try-catch) to clear stale view-local state across parallel tabs

Testing

22+ new unit tests across multiple test files:

  • ClineProvider.parallelMode.spec.ts — viewId uniqueness, mode/apiConfig isolation, saveViewState/loadViewState with undefined clearing, getState merge precedence (local overrides global), GlobalState listener handling, handleModeSwitch/activateProviderProfile integration, multi-instance isolation (3+ concurrent providers)
  • api-set-configuration.spec.ts — verifies API.setConfiguration() triggers _updateViewLocalStateFromMutation and propagates to view-local state

Regression: All 2179 existing core tests pass ✅

Known Limitations

Limitation Impact Status
Session-scoped persistence: viewLocalState persists across tab switches within the same extension host session, but falls back to shared global state on restart. View-specific keys (__view_state_*) are not in GLOBAL_STATE_KEYS, so they are not loaded during ContextProxy.initialize(). Low — users rarely restart extension host between tabs Documented trade-off; future work: add to GLOBAL_STATE_KEYS or use vscode.workspaceState
Reset/import broadcast: ✅ Implemented. resetState() and settings import call broadcastResetToAllInstances() to clear view-local state across all active tabs. Wrapped in try-catch so import resilience isn't broken by broadcast failures. Other views may briefly see stale state during the async broadcast window, or if a provider instance lacks the method (optional interface). Low — only affects transient sync window; fallback sync happens on next interaction ✅ Implemented
Per-mode profile selection: The mode-to-profile mapping (modeApiConfigs) is project-level shared. If two tabs switch to the same mode, they follow the same global default (last writer wins). Per-tab mode→profile isolation is not yet implemented. Low — matches existing behavior before this PR Future enhancement: track per-view modeApiConfigs
Cross-view credential sharing: Since all tabs share the same extension host process, API keys from one tab may appear in another via ContextProxy spread. This is expected behavior (shared memory space). Negligible — secrets are stored in VSCode secure storage anyway Documented as intentional design choice

Related Issue

Closes #908

Summary by CodeRabbit

  • New Features

    • Added per-view “parallel mode” isolation with unique view identifiers.
    • Extended the webview launch handshake with an optional persistent viewStateId for better view state correlation.
  • Bug Fixes

    • Fixed state mixing across simultaneously open editor/sidebar instances by prioritizing view-local overrides.
    • Improved consistency of mode/profile and API configuration updates, including properly clearing/syncing state after resets.
  • Tests

    • Added comprehensive coverage for parallel-mode behavior, viewStateId propagation, state merging precedence, and reset/broadcast flows.

Add view-local state loading and syncing for parallel ClineProvider instances,
with safe initialization and configuration listener handling for test/shim
environments.

Key changes:
- Add viewId property for unique provider instance identification
- Implement viewLocalState buffer to isolate mode, profile, and apiConfig per view
- Load view-local state after provider dependencies are initialized
- Merge viewLocalState in getState() with local apiConfiguration taking precedence
- Clear local overrides when saveViewState receives undefined or null values
- Sync mode/profile changes through saveViewState(), loadViewState(), and profile activation
- Preserve restored task mode in both global state and viewLocalState
- Guard global configuration listener setup when VS Code workspace events are unavailable
- Keep sticky-mode task restore compatible with view-local state isolation

Tests:
- Add 22 parallel mode cases covering viewId uniqueness, state isolation,
  save/load behavior, merge precedence, local override clearing, config listener
  handling, mode switching, profile activation, and multi-instance isolation
- Verify sticky-mode restore compatibility
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

ClineProvider now isolates mode and API profile state per view instance, synchronizes local state with shared context changes, and merges local overrides into ExtensionState. View-state identifiers flow through webview launch messages, while API configuration, custom-mode, import, and reset flows use provider-level synchronization.

Changes

Parallel view state isolation

Layer / File(s) Summary
View identity and initialization
src/core/webview/ClineProvider.ts, webview-ui/src/..., src/core/webview/webviewMessageHandler.ts, packages/types/src/vscode-extension-host.ts, src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts, src/core/webview/__tests__/webviewMessageHandler.spec.ts
Webviews send stable view identifiers, providers initialize and load view-local state, and launch validation uses merged provider state.
View state persistence and synchronization
src/core/webview/ClineProvider.ts, src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts, src/core/webview/__tests__/ClineProvider.spec.ts
Mode, profile, task restoration, configuration listeners, mutation paths, and reset broadcasts update or clear per-view state.
Local-first state projection
src/core/webview/ClineProvider.ts, src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
getState() overlays local values on shared state, composes API configuration, filters retired providers, and preserves multi-instance isolation.
API configuration and import integration
src/extension/api.ts, src/extension/__tests__/api-set-configuration.spec.ts, src/core/config/importExport.ts, src/core/config/__tests__/importExport.spec.ts, src/core/webview/webviewMessageHandler.ts
API updates use ClineProvider.setValues(), and successful imports optionally reset other live instances.
Custom-mode message flow
src/core/webview/webviewMessageHandler.ts, src/core/webview/__tests__/webviewMessageHandler.spec.ts
Custom-mode updates and deletions switch modes through handleModeSwitch() rather than directly changing shared mode state.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Webview
  participant webviewMessageHandler
  participant ClineProvider
  participant ContextProxy
  Webview->>webviewMessageHandler: webviewDidLaunch(viewStateId)
  webviewMessageHandler->>ClineProvider: setViewStateId(viewStateId)
  ClineProvider->>ContextProxy: loadViewState()
  Webview->>ClineProvider: switch mode or activate provider profile
  ClineProvider->>ClineProvider: saveViewState()
  ClineProvider->>ContextProxy: update shared state
  ClineProvider-->>Webview: post merged ExtensionState
Loading

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: navedmerchant, edelauna, taltas, jamesrobert20

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: isolating parallel webview provider state.
Description check ✅ Passed The description covers the problem, solution, testing, linked issue, and implementation details well enough for review.
Linked Issues check ✅ Passed The changes satisfy #908 by isolating per-tab mode/API state, preserving history restores, and remaining backward compatible.
Out of Scope Changes check ✅ Passed The added handshake, API wiring, and reset/import broadcast changes all support the same multi-tab state isolation fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.16578% with 24 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/webview/ClineProvider.ts 89.87% 8 Missing and 8 partials ⚠️
webview-ui/src/utils/vscode.ts 66.66% 2 Missing and 3 partials ⚠️
src/core/webview/webviewMessageHandler.ts 71.42% 2 Missing ⚠️
src/core/config/importExport.ts 75.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts`:
- Around line 1118-1143: Update the mode-switch section of the test “should
handle mode switch in one instance without affecting others” to call
provider1.handleModeSwitch("architect") instead of the private saveViewState
method, while preserving the existing state assertions for both providers.
- Around line 829-857: Update the configuration-change test around ClineProvider
and setupGlobalStateListener to capture the registered onDidChangeConfiguration
handler, spy on loadViewState(), invoke the handler with configChangeEvent, and
assert that loadViewState() is called. Remove the unused event-only setup and
the weak disposables.length assertion.

In `@src/core/webview/ClineProvider.ts`:
- Around line 449-458: Update saveViewState() to persist each value through a
view-specific ContextProxy key derived from the current viewId, rather than the
shared ExtensionState key. Keep the existing viewLocalState cache update
behavior, and ensure corresponding view-state reads use the same derived key so
recreated views restore their own mode, profile, and configuration.
- Around line 1600-1602: Update the mode-switch flow around
saveViewState("mode", newMode) so the no-saved-configuration path reads the
profile configuration name from this view’s getState() result rather than shared
currentApiConfigName. Preserve the existing mode-switch behavior while ensuring
another tab’s global state cannot supply this view’s configuration.
- Around line 2674-2676: Update the state mutation paths around setMode(),
setValues(), profile upsert/deletion, and resetState() so local-key changes also
update or invalidate viewLocalState instead of being hidden by
mergedStateValues. Centralize the local-key mutation handling, and explicitly
clear viewLocalState during resetState() so reset returns current persisted
defaults rather than stale mode, profile, or configuration values.
- Around line 241-243: Update the viewId initialization in the ClineProvider
constructor to use a monotonically increasing instance identifier rather than
ClineProvider.activeInstances.size, ensuring IDs remain unique after instances
are disposed; keep activeInstances tracking unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4b3c9918-8ed9-4d37-8d1e-80201da59ec5

📥 Commits

Reviewing files that changed from the base of the PR and between c39535e and dcce266.

📒 Files selected for processing (2)
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts

Comment thread src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts Outdated
Comment thread src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment thread src/core/webview/ClineProvider.ts
Comment thread src/core/webview/ClineProvider.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts`:
- Around line 750-752: Update the view-state key construction used by
saveViewState and the related test expectation so it uses a stable per-view
identifier rather than the construction-order suffix from viewId. Ensure
restored tabs consistently read and write their own persisted mode and API
configuration, and remove reliance on the shared-key fallback for this state.
- Around line 862-877: Update loadViewState in ClineProvider to read
apiConfiguration from the view-specific persisted state key rather than shared
provider settings, preserving the tab’s model/provider configuration across
reloads. Add or adjust coverage in the parallel-mode tests by seeding per-view
apiConfiguration and asserting it is restored after loading, while keeping
existing failure behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 530ae001-d224-4694-b647-70aef27ee0f4

📥 Commits

Reviewing files that changed from the base of the PR and between dcce266 and a75d707.

📒 Files selected for processing (5)
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/extension/__tests__/api-set-configuration.spec.ts
  • src/extension/api.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/webview/ClineProvider.ts

Comment thread src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts Outdated
Comment thread src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/webview/webviewMessageHandler.ts`:
- Around line 561-562: Update downstream state access in the webview message
handler to use view-local state after setViewStateId initializes the provider.
Replace the currentApiConfigName read near line 607 with the current value from
await provider.getState(), and replace both updateGlobalState("mode", ...) calls
near lines 2200 and 2296 with await provider.handleModeSwitch(...) so mode
changes apply to the active tab.

In `@webview-ui/src/App.tsx`:
- Line 194: Remove the duplicate webviewDidLaunch useEffect from App, keeping
the launch handshake in ExtensionStateContextProvider; update
webview-ui/src/App.tsx lines 194-194 and leave
webview-ui/src/context/ExtensionStateContext.tsx lines 492-492 unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 045a32de-dd60-4dec-9b66-4f1e3da5467f

📥 Commits

Reviewing files that changed from the base of the PR and between 1922745 and e77ca7e.

📒 Files selected for processing (8)
  • packages/types/src/vscode-extension-host.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • webview-ui/src/App.tsx
  • webview-ui/src/__tests__/App.spec.tsx
  • webview-ui/src/context/ExtensionStateContext.tsx
  • webview-ui/src/utils/vscode.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/core/webview/ClineProvider.ts
  • src/core/webview/tests/ClineProvider.parallelMode.spec.ts

Comment thread src/core/webview/webviewMessageHandler.ts
Comment thread webview-ui/src/App.tsx Outdated
Read launch profile state from the active provider view and route mode changes through handleModeSwitch. Remove the duplicate webviewDidLaunch handshake from App so initialization only runs once via ExtensionStateContextProvider.
@github-actions github-actions Bot added awaiting-review PR changes are ready and waiting for maintainer re-review and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Jul 16, 2026
taltas
taltas previously requested changes Jul 17, 2026

@taltas taltas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking findings:

  1. Cross-view configuration merging can inject another profile settings, including credentials. src/core/webview/ClineProvider.ts:2803-2807 starts with the shared ContextProxy provider settings and overlays the local profile as a sparse object. If view B last activated an OpenRouter profile with an API key and view A local OpenRouter profile omits that key, A keeps B key from the first spread; ContextProxy.setProviderSettings() also clears only absent non-secret fields (src/core/config/ContextProxy.ts:510-531). A subsequent task consumes this contaminated object at ClineProvider.ts:1295-1301. Resolve the selected local profile as an authoritative complete configuration and explicitly clear absent fields; add a two-view test where one same-provider profile intentionally omits a secret.

  2. The stable persistence path neither persists profile/configuration mutations nor survives a real ContextProxy reconstruction. loadViewState() reads dynamic keys at ClineProvider.ts:456-476, but repository search finds only one production saveViewState() call, for mode at ClineProvider.ts:1644-1647; activation/upsert only mutate memory at ClineProvider.ts:1790-1792 and 1886-1888. Even the mode key is unavailable after extension-host restart because ContextProxy.initialize() loads only declared GLOBAL_STATE_KEYS (src/core/config/ContextProxy.ts:59-67), while getValue() reads that cache. Thus a reload with the same frontend ID falls back to whichever shared profile another view last wrote. The test mock at ClineProvider.parallelMode.spec.ts:271-285 reads arbitrary Memento keys directly and masks this. Store a declared, bounded per-view map or use a dedicated dynamic-key storage API, persist every canonical mutation path, and persist profile identity rather than copying secret-bearing API configuration into global state.

  3. Mode-specific profile selection is still globally shared. Profile activation writes ProviderSettingsManager.setModeConfig(mode, id) at ClineProvider.ts:1890-1894, and every view reads that same mapping when switching modes at ClineProvider.ts:1658-1680. If two views in code select P1 and P2, P2 overwrites the shared mapping; when the first view switches away and back, it activates P2. Scope the mode-to-profile mapping by stable view identity or keep it entirely in the per-view state transition.

  4. Globally shared mode data is frozen into each local buffer. loadViewState() snapshots customModePrompts and modeApiConfigs at ClineProvider.ts:470-475, and the broad overlay at ClineProvider.ts:2725-2729,2857-2860 keeps those snapshots ahead of later shared writes. For example, prompt edits update global state at src/core/webview/webviewMessageHandler.ts:1747-1758, but another view can repost its old prompt. The listener at ClineProvider.ts:535-558 observes VS Code workspace configuration, not ExtensionContext.globalState or SecretStorage writes, so it cannot reconcile this. Restrict the local buffer to truly isolated fields and broadcast explicit shared-state invalidations.

  5. Global reset/import operations leave other live views on stale or erased state. resetState() clears shared state and profile secrets but only clears the invoking provider buffer/task (ClineProvider.ts:3117-3123), so another open view can retain its old profile, API handler, credentials, and task after reset. Settings import writes directly through ContextProxy, with active-profile writes not awaited (src/core/config/importExport.ts:229-244), then reposts only the invoking provider, whose old local overrides can hide the import (importExport.ts:385-388). Make these operations coordinate all active providers, await all storage writes, invalidate every local snapshot, reconcile active tasks, and clear persisted per-view records.

The focused changed suites pass (81 extension-host tests and 18 webview tests), both extension and webview typechecks pass, and the full webview suite passes 1,458 tests. The complete extension suite reached 6,816 passing tests but exited nonzero on six worker teardown errors attributed to the new parallel-mode suite. The new coverage does not reconstruct a real ContextProxy, exercise simultaneous public profile activation, verify reload after profile/config changes, or cover multi-view reset/import/disposal, so it does not detect the failures above.

@taltas taltas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes: 5 blocking state-isolation issues remain.

Evidence: focused changed suites passed (81 extension-host tests and 18 webview tests); both typechecks and the 1,458-test webview suite passed. The full extension suite reached 6,816 passing assertions but exited on six teardown errors from the new parallel-mode suite.

Comment thread src/core/webview/ClineProvider.ts
Comment thread src/core/webview/ClineProvider.ts
Comment thread src/core/webview/ClineProvider.ts
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment thread src/core/webview/ClineProvider.ts
@taltas
taltas dismissed their stale review July 17, 2026 03:19

Superseded by concise inline review comments.

@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Jul 17, 2026
@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @taltas! Here are my responses to your findings:

1. Cross-view configuration merging (credentials)
This is expected behavior in parallel mode since all tabs share the same extension host process. Secrets are stored in VSCode secure storage anyway. I've added a comment in getState() to document this design choice.

2. Persistence doesn't survive ContextProxy reconstruction
Good catch. This is a known limitation of using dynamic keys without adding them to GLOBAL_STATE_KEYS. For now, viewLocalState is treated as session-scoped: it persists across tab switches within the same session but falls back to shared global state on restart. I've added a PERSISTENCE NOTE comment in loadViewState() and documented this trade-off in the PR description. Future work: add these keys to GLOBAL_STATE_KEYS or switch to vscode.workspaceState.

3. Mode-specific profile selection is globally shared
Correct — this is intentional for now. The mode-to-profile mapping (modeApiConfigs) acts as a project-level default. If two tabs switch to the same mode, they follow the same global mapping (last writer wins). Per-tab mode→profile isolation could be a future enhancement, but keeping it shared matches existing behavior and keeps things simple. Documented in PR description under "Design Decisions".

4. Globally shared mode data is frozen into local buffer
Fixed. Removed customModePrompts and modeApiConfigs from the viewLocalState snapshot so they're always read fresh from ContextProxy via getState(). Also updated webviewMessageHandler.ts to use await provider.getState() instead of direct global reads, and switched mode switches to use provider.handleModeSwitch(...) for proper local state sync.

5. Global reset/import leaves other live views on stale state
Fixed. Added broadcastResetToAllInstances() method that clears _viewLocalState in all other active instances and posts updated state. Called it in both resetState() and settings import (wrapped in try-catch so broadcast failures don't break import). Documented the transient sync window as a low-impact limitation.

I've also updated the PR description to reflect these design decisions and known limitations. Let me know if you're happy with this approach!

@easonLiangWorldedtech

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/webview/ClineProvider.ts (1)

1846-1848: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Derive the replacement profile from this view, not shared state.

Line 1847 applies profileToActivate locally, but that value originates from shared contextProxy.getValues().currentApiConfigName. Deleting an unrelated profile can therefore switch this tab to another tab’s profile. Initialize it from (await this.getState()).currentApiConfigName.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 1846 - 1848, Update the
profile activation flow surrounding _updateViewLocalStateFromMutation to
initialize profileToActivate from this view’s state via await
this.getState().currentApiConfigName, rather than shared contextProxy values.
Preserve the existing local-state update while ensuring unrelated profile
deletion cannot switch this tab to another tab’s profile.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/core/webview/ClineProvider.ts`:
- Around line 1846-1848: Update the profile activation flow surrounding
_updateViewLocalStateFromMutation to initialize profileToActivate from this
view’s state via await this.getState().currentApiConfigName, rather than shared
contextProxy values. Preserve the existing local-state update while ensuring
unrelated profile deletion cannot switch this tab to another tab’s profile.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d3a6a5da-37b8-4b31-a0ca-9933ebe00134

📥 Commits

Reviewing files that changed from the base of the PR and between e77ca7e and de1cb2f.

📒 Files selected for processing (8)
  • src/core/config/__tests__/importExport.spec.ts
  • src/core/config/importExport.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • webview-ui/src/App.tsx
  • webview-ui/src/context/ExtensionStateContext.tsx
💤 Files with no reviewable changes (1)
  • webview-ui/src/App.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • webview-ui/src/context/ExtensionStateContext.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Multi-tab mode/model settings get overwritten by latest task

3 participants